package io.gitee.zhangbinhub.acp.cloud.resource.server

import cn.dev33.satoken.SaManager
import cn.dev33.satoken.context.SaHolder
import cn.dev33.satoken.dao.SaTokenDao
import cn.dev33.satoken.dao.SaTokenDaoForRedisson
import cn.dev33.satoken.exception.BackResultException
import cn.dev33.satoken.exception.NotLoginException
import cn.dev33.satoken.`fun`.SaFunction
import cn.dev33.satoken.`fun`.strategy.SaCorsHandleFunction
import cn.dev33.satoken.oauth2.annotation.handler.SaCheckAccessTokenHandler
import cn.dev33.satoken.oauth2.annotation.handler.SaCheckClientIdSecretHandler
import cn.dev33.satoken.oauth2.annotation.handler.SaCheckClientTokenHandler
import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts
import cn.dev33.satoken.oauth2.template.SaOAuth2Util
import cn.dev33.satoken.router.SaHttpMethod
import cn.dev33.satoken.router.SaRouter
import cn.dev33.satoken.serializer.impl.SaSerializerTemplateForJdkUseBase64
import cn.dev33.satoken.solon.integration.SaTokenInterceptor
import cn.dev33.satoken.stp.StpUtil
import cn.dev33.satoken.strategy.SaAnnotationStrategy
import cn.dev33.satoken.strategy.SaStrategy
import cn.dev33.satoken.util.SaTokenConsts
import io.gitee.zhangbinhub.acp.boot.conf.AcpCorsConfiguration
import io.gitee.zhangbinhub.acp.boot.exceptions.AcpWebExceptionHandler
import io.gitee.zhangbinhub.acp.boot.http.HttpHeaders
import io.gitee.zhangbinhub.acp.cloud.constant.RestPrefix
import io.gitee.zhangbinhub.acp.cloud.resource.server.component.AuthGlobalFilter
import io.gitee.zhangbinhub.acp.cloud.resource.server.component.AuthPermission
import io.gitee.zhangbinhub.acp.cloud.resource.server.conf.AcpCloudResourceServerConfiguration
import io.gitee.zhangbinhub.acp.cloud.resource.server.exceptions.AcpCloudResourceServerWebExceptionHandler
import io.gitee.zhangbinhub.acp.cloud.resource.server.tools.TokenTools
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.acp.core.common.log.LogFactory
import org.noear.solon.Solon
import org.noear.solon.annotation.Bean
import org.noear.solon.annotation.Configuration
import org.noear.solon.annotation.Import
import org.noear.solon.annotation.Inject
import org.redisson.api.RedissonClient

@Configuration
@Import(AcpCloudResourceServerConfiguration::class)
class AcpCloudResourceServerAutoConfiguration {
    private val log = LogFactory.getInstance(this.javaClass)

    @Bean
    fun acpCloudResourceServerExceptionHandler(): AcpWebExceptionHandler = AcpCloudResourceServerWebExceptionHandler()

    @Bean
    fun authPermission(): AuthPermission = AuthPermission()

    @Bean
    fun saTokenDao(@Inject redissonClient: RedissonClient): SaTokenDao {
        return SaTokenDaoForRedisson(redissonClient)
    }

    @Bean
    fun saTokenSerializer() {
        SaManager.setSaSerializerTemplate(SaSerializerTemplateForJdkUseBase64())
    }

    @Bean(index = SaTokenConsts.SA_TOKEN_CONTEXT_FILTER_ORDER + 1)
    fun authGlobalFilter(): AuthGlobalFilter = AuthGlobalFilter()

    @Bean
    fun saTokenInterceptor(
        @Inject acpCloudResourceServerConfiguration: AcpCloudResourceServerConfiguration,
        @Inject acpCorsConfiguration: AcpCorsConfiguration
    ): SaTokenInterceptor =
        SaTokenInterceptor().apply {
            permitAllPath(acpCloudResourceServerConfiguration).forEach { uri -> log.info("permitAll uri: $uri") }
            this.addInclude(ALL_PATH)
            this.setAuth {
                SaRouter.notMatch(SaHttpMethod.OPTIONS)
                    .notMatch(permitAllPath(acpCloudResourceServerConfiguration))
                    .notMatch(permitOauthApi())
                    .match(ALL_PATH, SaFunction {
                        val accessToken = TokenTools.getAccessTokenModel()
                        val clientToken = TokenTools.getClientTokenModel()
                        if (accessToken != null) {
                            SaOAuth2Util.checkAccessToken(accessToken.accessToken)
                            try {
                                StpUtil.checkLogin()
                            } catch (_: NotLoginException) {
                                throw NotLoginException("token 无效", "", "-2").setCode(11012)
                            }
                        }
                        if (clientToken != null) {
                            SaOAuth2Util.checkClientToken(clientToken.clientToken)
                        }
                        if (accessToken == null && clientToken == null) {
                            throw NotLoginException("token 无效", "", "-2").setCode(11012)
                        }
                    })
            }
            this.setBeforeAuth {
                SaHolder.getResponse()
                    // 是否可以在iframe显示视图： DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                    .setHeader("X-Frame-Options", "SAMEORIGIN")
                    // 是否启用浏览器默认XSS防护： 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时，停止渲染页面
                    .setHeader("X-XSS-Protection", "1; mode=block")
                    // 禁用浏览器内容嗅探
                    .setHeader("X-Content-Type-Options", "nosniff")
            }
        }.also {
            SaAnnotationStrategy.instance.registerAnnotationHandler(SaCheckAccessTokenHandler())
            SaAnnotationStrategy.instance.registerAnnotationHandler(SaCheckClientIdSecretHandler())
            SaAnnotationStrategy.instance.registerAnnotationHandler(SaCheckClientTokenHandler())
            // 跨域处理
            if (acpCorsConfiguration.enabled) {
                SaStrategy.instance.corsHandle = saCorsHandleFunction(acpCorsConfiguration)
            }
        }

    private fun saCorsHandleFunction(acpCorsConfiguration: AcpCorsConfiguration): SaCorsHandleFunction =
        SaCorsHandleFunction { request, response, _ ->
            val origin = request.getHeader(HttpHeaders.ORIGIN)
            if (CommonTools.isNullStr(origin)) {
                return@SaCorsHandleFunction
            }
            response.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, acpCorsConfiguration.maxAge.toString())
            if (acpCorsConfiguration.allowCredentials) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")
            }
            if (acpCorsConfiguration.allowedMethods.isEmpty()) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "*")
            } else {
                response.setHeader(
                    HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS,
                    acpCorsConfiguration.allowedMethods.joinToString(separator = ",")
                )
            }
            if (acpCorsConfiguration.allowedHeaders.isEmpty()) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*")
            } else {
                response.setHeader(
                    HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
                    acpCorsConfiguration.allowedHeaders.joinToString(separator = ",")
                )
            }
            if (acpCorsConfiguration.exposedHeaders.isEmpty()) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*")
            } else {
                response.setHeader(
                    HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
                    acpCorsConfiguration.exposedHeaders.joinToString(separator = ",")
                )
            }
            if (acpCorsConfiguration.allowedOrigins.contains("*") || acpCorsConfiguration.allowedOrigins.contains(
                    origin
                )
            ) {
                response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin)
            } else {
                throw BackResultException("Invalid CORS request")
            }
        }

    companion object {
        private const val ALL_PATH = "/**"

        /**
         * 放行安全检查的开放Path
         */
        fun permitAllPath(acpCloudResourceServerConfiguration: AcpCloudResourceServerConfiguration) =
            mutableListOf<String>().apply {
                this.add("${Solon.cfg().serverContextPath()}/error")
                this.add("${Solon.cfg().serverContextPath()}/favicon.ico")
                this.add("${Solon.cfg().serverContextPath()}/swagger/**")
                this.add("${Solon.cfg().serverContextPath()}/swagger-resources")
                this.add("${Solon.cfg().serverContextPath()}/v3/api-docs/**")
                this.add("${Solon.cfg().serverContextPath()}/doc.html")
                this.add("${Solon.cfg().serverContextPath()}/webjars/**")
                this.add("/solon-admin/api/monitor/data")
                this.add("/healthz")
                acpCloudResourceServerConfiguration.permitAllPath.forEach { path ->
                    this.add(
                        Solon.cfg().serverContextPath() + path
                    )
                }
                this.add(Solon.cfg().serverContextPath() + RestPrefix.OPEN + "/**")
            }

        fun permitOauthApi() = mutableListOf<String>().apply {
            this.add(Solon.cfg().serverContextPath() + SaOAuth2Consts.Api.authorize)
            this.add(Solon.cfg().serverContextPath() + SaOAuth2Consts.Api.token)
            this.add(Solon.cfg().serverContextPath() + SaOAuth2Consts.Api.refresh)
            this.add(Solon.cfg().serverContextPath() + SaOAuth2Consts.Api.revoke)
            this.add(Solon.cfg().serverContextPath() + SaOAuth2Consts.Api.client_token)
            this.add(Solon.cfg().serverContextPath() + SaOAuth2Consts.Api.doLogin)
            this.add(Solon.cfg().serverContextPath() + SaOAuth2Consts.Api.doConfirm)
        }
    }
}